
import tasktemplate
import math
import types
import servertime
import datetime
import logging
import protocol

IDLE,\
WAITINGTORUN,\
RUNING,\
COMPLETION,\
CANCELING,\
CANCELED,\
NORUN = range(7)

BEGIN,\
SUCCESS,\
FAILURE,\
EXCEPTION =range(4)

class RunInfo():
    def __init__(self, machine, type, info, args, subid):
        self.machine = machine
        self.type = type
        self.info = info
        self.args = args
        self.subid = subid


class Task():
    def __init__(self, tempid, template):
        if tempid == None:
            raise ValueError('The id must not be None ')
        
        if template == None:
            raise ValueError('The template must not be None ')
        
        self.status = IDLE
        self.id  = None
        self.template = template
        self.result = None
        self.tempid = tempid
        self.end_time = None
        self.run_machine_id = []
        self.start_disconnect_time = None
        self.name = None
        self.begin_run_time = None
        self.result_message = ''
        self.subtasks = {}
        self.addtional_mail_receiver = ''
        self.create_time = None
        
    def GettingStart(self, taskmanager):
        self.SetStatus(WAITINGTORUN)
        return True
        
    
    def GetStatus(self):
        return self.status
    
    def SetStatus(self, status):
        self.status = status       
    
    def Start(self, machinelist, starttime):
        assert(len(machinelist) > 0)
        self.SetStatus(RUNING)
        self.begin_run_time = starttime
        
        run_infos = []
        if self.template.is_parallel == True:
            paramlist = self.SplitTaskParam(len(machinelist))
            index = 0
            for v in paramlist:
                run_info = RunInfo(machinelist[index], self.template.type, self.template.run_info, v, index + 1)
                run_infos.append(run_info)
                index += 1    
        else:
            run_info = RunInfo(machinelist[0], self.template.type, self.template.run_info, self.GetArgs(), 1)
            run_infos.append(run_info)
        
        for v in run_infos:
            self.subtasks[v.subid] = SubTask(self, v.subid, v.machine, starttime)
            v.machine.AddTask(self.template.id, self.id, v.subid)
        return run_infos
            
    def SplitTaskParam(self, count):
        if self.template.paralleltyep == tasktemplate.pt_global_index:
            return ['{0} --total_shards={1}, --shard_index={2}'.format(self.template.args, count, v) for v in range(count) ]
        else:
            raise NotImplementedError('normal task not support split by param_combin')
        
    def Finish(self, subid, endtime, respond):
        if subid not in self.subtasks:
            raise ValueError('sub task id not exits')
        
        sub = self.subtasks[subid]
        sub.OnFinish(respond.result, respond.message, endtime)
        
        if isinstance(respond.addtional_mail_receiver, types.StringTypes):
            self.addtional_mail_receiver = self.addtional_mail_receiver + ";" + respond.addtional_mail_receiver 
        
        for v in self.subtasks.values():
            if v.status == RUNING:
                return False
        
        self.status = COMPLETION
        self.end_time = endtime
        
        results = set()
        for v in self.subtasks.values():
            results.add(v.result)
            if type(v.result_message) == types.StringType:
                self.result_message += '{0}:{1}'.format(v.id, v.result_message)
        
        if len(results) == 1:
            self.result = results.pop()
        elif EXCEPTION in results:
            self.result = EXCEPTION
        elif FAILURE in results:
            self.result = FAILURE
        return True
    
    
    def AllReadyToRun(self):
        return self.status ==  WAITINGTORUN
    
    def Terminat(self):
        if self.status != WAITINGTORUN and self.status != RUNING:
            raise ValueError('error status')
        
        self.result_message = 'termination by user'
        if self.status  == WAITINGTORUN:
            self.SetStatus(CANCELED)
        else:
            self.SetStatus(CANCELING)
        
        machinelist = {}
        for v in self.subtasks.values():
            if v.status == RUNING:
                v.status = CANCELING
                machinelist[v.run_machine] = v.id
        
        return machinelist
    
    def OnSubTerminatedInfo(self, subid, endtime):
        sub = self.subtasks[subid]
        sub.OnTeminated(endtime)
        
        for v in self.subtasks.values():
            if v.status == RUNING:
                return 
        
        for v in self.subtasks.values():
            if type(v.result_message) == types.StringType:
                self.result_message += '{0}:{1}'.format(v.id, v.result_message)
        
        self.status = CANCELED
        self.end_time = endtime
        self.result = EXCEPTION
        
        return 
    
    
    def Breath(self):
        for v in self.subtasks.values():
            if v.start_disconnect_time and \
            (servertime.current_time - v.start_disconnect_time) > datetime.timedelta(minutes = 30):
                respond = protocol.C2STaskFinish()
                respond.message = "run machine disconnect"
                respond.result = EXCEPTION
                self.Finish(v.id, servertime.current_time, respond)
    
    def GetArgs(self):
        return self.template.args
    
    def IsFinish(self):
        return self.status == COMPLETION or self.status == CANCELED or self.status == NORUN 
    
    def SetTimeout(self, now):
        self.begin_run_time = self.create_time
        self.end_time = now
        self.status = NORUN
        self.result = EXCEPTION
        self.result_message = 'not find machine to run'
        

class CaseTask(Task):
    def __init__(self, tempid, template):
        Task.__init__(self, tempid, template)
        self.case_param = None
        self.casesql = template.casesql
        
    def GettingStart(self, manager):
        self.SetStatus(WAITINGTORUN)
        manager.CaseDataRequset(self.id, self.casesql)
    
    def SetCase(self, cases):
        self.case_param = [v for v in cases if v != None]
        
        
    def AllReadyToRun(self):
        return self.status ==  WAITINGTORUN and (self.case_param != None)
    
    
    def SplitTaskParam(self, count):
        if self.template.paralleltyep == tasktemplate.pt_global_index:
            return ['{0} --total_shards={1} --shard_index={2}'.format(self.template.args, count, v) for v in range(count) ]
        elif self.template.paralleltyep == tasktemplate.pt_param_combin:
            step = math.trunc(len(self.case_param) / count)
            if step == 0: step = 1
            return [self.template.args + ' --case_param=' + ':'.join(self.case_param[i:i+step]) \
                    for i in range(0, len(self.case_param), step)]
        else:
            raise NotImplementedError('split type not support ')
            
    def GetArgs(self):
        return self.template.args + ' --case_param=' + ':'.join(self.case_param)
    
    
class SubTask():
    def __init__(self, task, id, run_machine, starttime):
        if task == None:
            raise ValueError('task must be not none')
        
        if id == None:
            raise ValueError('task must be not none')
        
        self.task = task
        self.id = id
        self.run_machine = run_machine.dbid
        self.ip = run_machine.ip_addr
        self.mac_addr = run_machine.mac_addr
        self.status = RUNING
        self.start_time = starttime
        self.end_time = None
        self.result = None
        self.result_message = None
        self.start_disconnect_time = None
        
    def OnFinish(self, result, resultmessage, endtime):
        self.result = result
        self.result_message = resultmessage
        self.end_time = endtime
        self.status = COMPLETION
    
    def OnTeminated(self, endtime):
        self.result = EXCEPTION
        self.result_message = 'termination by user'
        self.end_time = endtime
        self.status = CANCELED
        